package com.crazymaker.springcloud.standard.ratelimit;

import cn.hutool.core.io.FileUtil;
import com.crazymaker.springcloud.common.distribute.rateLimit.RateLimitService;
import com.crazymaker.springcloud.standard.properties.RedisRateLimitProperties;
import com.google.common.collect.ImmutableList;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.core.script.RedisScript;

import java.nio.charset.Charset;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;

@Slf4j
public class RedisRateLimitImpl implements RateLimitService, InitializingBean
{

    private ScheduledExecutorService executorService = Executors.newScheduledThreadPool(1);

    private RedisRateLimitProperties redisRateLimitProperties;

    private RedisTemplate redisTemplate;

    RedisScript<Long> rateLimiterScript = null;

    public RedisRateLimitImpl(
            RedisRateLimitProperties redisRateLimitProperties,
            RedisTemplate redisTemplate)
    {
        this.redisRateLimitProperties = redisRateLimitProperties;
        this.redisTemplate = redisTemplate;
    }

    private Map<String, RedisRateLimitProperties.TypeInfo> typeInfoMap = new HashMap<>();
    private Map<String, RedisScript<Long>> scriptMap = new HashMap<>();

    /**
     * 是否超过redis 计数限制器的限制
     *
     * @param cacheKey 计数器的key
     * @return true or false
     */
    @Override
    public Boolean isLimitExceeded(String cacheKey)
    {
        if (cacheKey == null)
        {
            return true;
        }
        if (cacheKey.indexOf(":" ) <= 0)
        {
            cacheKey = "default:" + cacheKey;
        }
        RedisRateLimitProperties.TypeInfo typeInfo = typeInfoMap.get(cacheKey);
        if (typeInfo == null)
        {
            return true;
        }

        Long currMillSecond = (Long) redisTemplate.execute(new RedisCallback<Long>()
        {
            @Override
            public Long doInRedis(RedisConnection connection) throws DataAccessException
            {
                return connection.time();
            }
        });
        RedisScript<Long> rateLimiterClientLua = scriptMap.get(cacheKey);
        Long acquire = (Long) redisTemplate.execute(rateLimiterClientLua,
                ImmutableList.of(typeInfo.fullKey()),
                "acquire",
                "1",
                currMillSecond.toString());

        if (acquire == 1)
        {
            return false;
        }
        return true;
    }


    @Override
    public void afterPropertiesSet() throws Exception
    {
/*        executorService.scheduleAtFixedRate(new Runnable() {
            @Override
            public void run() {*/
        try
        {

            String script = FileUtil.readString(redisRateLimitProperties.getRateLimitLua(), Charset.forName("UTF-8" ));
            rateLimiterScript = new DefaultRedisScript<>(script, Long.class);


            List<RedisRateLimitProperties.TypeInfo> typeInfos = redisRateLimitProperties.getTypeInfos();
            if (null == typeInfos)
            {
                return;
            }
            for (RedisRateLimitProperties.TypeInfo typeInfo : typeInfos)
            {
                initOneType(typeInfo);
            }
            log.info("redis rate limit inited !" );
        } catch (Exception e)
        {
            log.error("redis rate limit  error.....", e);
        }
     /*       }
        }, 2, 1, TimeUnit.MINUTES);*/
    }

    private void initOneType(RedisRateLimitProperties.TypeInfo typeInfo)
    {
        Long result = (Long) redisTemplate.execute(rateLimiterScript,
                ImmutableList.of(typeInfo.fullKey()),
                "init",
                typeInfo.getMaxPermits().toString(),
                typeInfo.getRate().toString());
        typeInfoMap.put(typeInfo.cashKey(), typeInfo);
        scriptMap.put(typeInfo.cashKey(), rateLimiterScript);
    }

}
